#include "interrupt.h"
#include "io.h"
#include "global.h"
#include "print.h"

#define IDT_DESC_COUNT 33

extern void* intr_entry_table[IDT_DESC_COUNT];

// external interrupt controller port
#define PIC_M_CTRL 0x20
#define PIC_M_DATA 0x21
#define PIC_S_CTRL 0xa0
#define PIC_S_DATA 0xa1

#define EFLAGS_IF   0x00000200
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR))

struct intr_gate_desc
{
	uint16_t		func_offset_low_word;
	uint16_t		selector;
	uint8_t			dcount;
	uint8_t			attribute;
	uint16_t		func_offset_high_word;
};

intr_handler intr_handler_table[IDT_DESC_COUNT];
const char* intr_name[IDT_DESC_COUNT];
struct intr_gate_desc idt[IDT_DESC_COUNT];

static void idt_desc_init()
{
	int i;
	for (i = 0; i < IDT_DESC_COUNT; ++i)
	{
		idt[i].func_offset_low_word = (uint32_t)intr_entry_table[i] & 0x0000FFFF;
		idt[i].selector = SELECTOR_K_CODE;
		idt[i].dcount = 0;
		idt[i].attribute = IDT_DESC_ATTR_DPL0;
		idt[i].func_offset_high_word = ((uint32_t)intr_entry_table[i] & 0xFFFF0000) >> 16;
	}
}

static void pic_init()
{
	// init main
	outb (PIC_M_CTRL, 0x11);   // ICW1
	outb (PIC_M_DATA, 0x20);   // ICW2: interrupt num begin from 0x20
	outb (PIC_M_DATA, 0x04);   // ICW3
	outb (PIC_M_DATA, 0x01);   // ICW4

	// init slave
	outb (PIC_S_CTRL, 0x11);    // ICW1
	outb (PIC_S_DATA, 0x28);    // ICW2: interrupt num begin from 0x28
	outb (PIC_S_DATA, 0x02);    // ICW3
	outb (PIC_S_DATA, 0x01);    // ICW4

	// set mask: only IR0
	outb (PIC_M_DATA, 0xfe);
	outb (PIC_S_DATA, 0xff);
}

static void general_intr_handler(uint8_t intr_num)
{
	if (intr_num == 0x27 || intr_num == 0x2f)
	{
		return;
	}
	
	set_cursor(0);
	int cursor_pos = 0;
	while(cursor_pos < 320)
	{
		print_char(' ');
		cursor_pos++;
	}

	set_cursor(0);
	print_string("!!!!!!!      excetion message begin  !!!!!!!!\n");
	set_cursor(88);
	print_string(intr_name[intr_num]);
	print_char(' ');
	print_string("intr_num: ");
	print_hex(intr_num);
	if (intr_num == 14)
	{
		int page_fault_vaddr = 0; 
		asm volatile ("movl %%cr2, %0" : "=r" (page_fault_vaddr));
		print_string("\npage fault addr is ");
		print_hex(page_fault_vaddr); 
	}
	print_string("\n!!!!!!!      excetion message end    !!!!!!!!\n");
   
	while(1);
}

static void exception_init(void)
{
	int i;
	for (i = 0; i < IDT_DESC_COUNT; ++i)
	{
		intr_handler_table[i] = (intr_handler)general_intr_handler;
		intr_name[i] = "unknown";
	}

	intr_name[0] = "#DE Divide Error";
	intr_name[1] = "#DB Debug Exception";
	intr_name[2] = "NMI Interrupt";
	intr_name[3] = "#BP Breakpoint Exception";
	intr_name[4] = "#OF Overflow Exception";
	intr_name[5] = "#BR BOUND Range Exceeded Exception";
	intr_name[6] = "#UD Invalid Opcode Exception";
	intr_name[7] = "#NM Device Not Available Exception";
	intr_name[8] = "#DF Double Fault Exception";
	intr_name[9] = "Coprocessor Segment Overrun";
	intr_name[10] = "#TS Invalid TSS Exception";
	intr_name[11] = "#NP Segment Not Present";
	intr_name[12] = "#SS Stack Fault Exception";
	intr_name[13] = "#GP General Protection Exception";
	intr_name[14] = "#PF Page-Fault Exception";
	intr_name[16] = "#MF x87 FPU Floating-Point Error";
	intr_name[17] = "#AC Alignment Check Exception";
	intr_name[18] = "#MC Machine-Check Exception";
	intr_name[19] = "#XF SIMD Floating-Point Exception";
}

void idt_init()
{
	idt_desc_init();

	exception_init();
	
	pic_init();

	// load idt
	uint64_t idtr = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));
	asm volatile (
		"lidt %0;"
		: 
		: "m"(idtr)
		);

	print_string("idt_init done\n");
}

enum intr_status intr_enable(void)
{
	enum intr_status old_status;

	if (INTR_ON == intr_get_status())
	{
		old_status = INTR_ON;
		return old_status;
	}
	else
	{
		old_status = INTR_OFF;
		asm volatile ("sti");
		return old_status;
	}
}

enum intr_status intr_disable(void)
{     
	enum intr_status old_status;

	if (INTR_ON == intr_get_status())
	{
		old_status = INTR_ON;
		asm volatile ("cli" : : : "memory");
		return old_status;
	}
	else
	{
		old_status = INTR_OFF;
		return old_status;
	}
}

enum intr_status intr_set_status(enum intr_status status)
{
	return status & INTR_ON ? intr_enable() : intr_disable();
}

enum intr_status intr_get_status()
{
	uint32_t eflags = 0; 
	GET_EFLAGS(eflags);
	return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}

int8_t register_intr_handler(uint8_t intr_num, intr_handler function)
{
	if (intr_num >= IDT_DESC_COUNT)
		return -1;
	
	intr_handler_table[intr_num] = function;
	
	return 0;
}
